package com.hu.common.util.adm;


import com.hu.common.enums.MessageEnum;
import com.hu.common.exception.BizException;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.DigestUtils;

import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;

/**
 * @author 355760
 * @ClassName OpenApiUtil
 * @Description token工具类
 */
public class OpenApiUtil {
    private static final Logger logger = LoggerFactory.getLogger(OpenApiUtil.class);
    private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";
    private static final Integer DEFAULT_SECURE_KEY_LENGTH = 16;
    private static final List<Integer> VALID_PWD_CHARS = new ArrayList<>();

    private static final String ENCRYPT_KEY = "4h!@w$rng,i#$@x1%)5^3(7*5P31/Ee0";
    private static final String GCM_ALGORITHMSTR = "AES/GCM/NoPadding";
    private static final String CBC_ALGORITHMSTR = "AES/CBC/PKCS5Padding";
    private static final String GCM_ADD = "sotif";
    private static final byte[] IV = { 0x41, 0x72, 0x65, 0x79, 0x6F, 0x75, 0x6D, 0x79, 0x53, 0x6E, 0x6F, 0x77, 0x6D, 0x61, 0x6E, 0x3F };

    static {
        // 0-9
        IntStream.rangeClosed('0', '9').forEach(VALID_PWD_CHARS::add);
        // A-Z
        IntStream.rangeClosed('A', 'Z').forEach(VALID_PWD_CHARS::add);
        // a-z
        IntStream.rangeClosed('a', 'z').forEach(VALID_PWD_CHARS::add);
    }

    /**
     * 创建token
     *
     * @param account  账号
     * @param password 用户密码
     */
    public static String createToken(String account, String password) {
        String str = "account=" + account + "password=" + password + "ts=" + System.currentTimeMillis();
        return MD5(str);
    }

    public static String MD5(String str) {
        return DigestUtils.md5DigestAsHex(str.getBytes(StandardCharsets.UTF_8));
    }

    // base64 编码
    public static String base64Encode(byte[] bytes) {
        return Base64.encodeBase64String(bytes);
    }

    // base64 解码
    public static byte[] base64Decode(String base64Code) {
        return Base64.decodeBase64(base64Code);
    }

    // AES 编码 Bytes
    public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        kgen.init(128);
        Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));

        return cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
    }

    // AES 编码 String
    public static String aesEncrypt(String content, String encryptKey) throws Exception {
        return base64Encode(aesEncryptToBytes(content, encryptKey));
    }

    // AES 解码 Bytes
    public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        kgen.init(128);

        Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));
        byte[] decryptBytes = cipher.doFinal(encryptBytes);

        return new String(decryptBytes);
    }

    /**
     * AES 解码 String
     * @param encryptStr 加密字符串
     * @param decryptKey 密钥
     * @return String
     * @throws Exception Exception
     */
    public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {
        return aesDecryptByBytes(base64Decode(encryptStr), decryptKey);
    }

    // 获取随机密钥字符
    public static String getSecureRandomStr(Integer keyLength) {
        if (!Assert.notEmpty(keyLength)) {
            keyLength = DEFAULT_SECURE_KEY_LENGTH;
        }

        StringBuilder sb = new StringBuilder();
        new SecureRandom().ints(keyLength, 0, VALID_PWD_CHARS.size())
                .map(VALID_PWD_CHARS::get).forEach(s -> sb.append((char) s));

        return sb.toString();
    }

    // 获取AES GCM算法加密字符串
    public static String aesEncryptGCM(String str) {
        try {
            Cipher cipher = Cipher.getInstance(GCM_ALGORITHMSTR);
            SecretKeySpec secretKeySpec = new SecretKeySpec(ENCRYPT_KEY.getBytes(), "AES");
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, IV);
            cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec, gcmParameterSpec);

            cipher.updateAAD(GCM_ADD.getBytes());
            byte[] result = cipher.doFinal(str.getBytes());

            return Base64.encodeBase64String(result);
        } catch (Exception e) {
            logger.error("[encryptGCM exception]: {}", E2s.exception2String(e));
            throw new BizException(MessageEnum.EXCEPTION.getMessage());
        }
    }

    // 解析AES GCM算法解码字符串
    public static String aesDecryptGCM(String data) {
        try {
            Cipher cipher = Cipher.getInstance(GCM_ALGORITHMSTR);
            SecretKeySpec secretKeySpec = new SecretKeySpec(ENCRYPT_KEY.getBytes(), "AES");
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, IV);

            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
            cipher.updateAAD(GCM_ADD.getBytes());
            byte[] bytes = Base64.decodeBase64(data.getBytes());
            byte[] result = cipher.doFinal(bytes);

            return new String(result, StandardCharsets.UTF_8);
        } catch (Exception e) {
            if (e instanceof IllegalBlockSizeException) {
                throw new BizException(MessageEnum.USER_ACCOUNT_PASSWORD_INVALID.getMessage());
            }

            logger.error("[decryptGCM exception]: {}", E2s.exception2String(e));
            throw new BizException(MessageEnum.EXCEPTION.getMessage());
        }

    }

    /**
     * AES CBC加密
     * @param source 待加密字符串
     * @return 加密后字符串
     */
    public static String aesEncryptCBC(String source)  {
        try {
            Cipher cipher = Cipher.getInstance(CBC_ALGORITHMSTR);
            IvParameterSpec iv = new IvParameterSpec(IV);
            byte[] keyBytes = ENCRYPT_KEY.getBytes(StandardCharsets.UTF_8);

            SecretKeySpec skySpec = new SecretKeySpec(keyBytes, "AES");
            cipher.init(Cipher.ENCRYPT_MODE, skySpec, iv);
            byte[] bytes = cipher.doFinal(source.getBytes());

            return Base64.encodeBase64String(bytes);
        } catch (Exception e) {
            logger.error("[aesEncryptCBC exception]: {}", E2s.exception2String(e));
            throw new BizException(MessageEnum.EXCEPTION.getMessage());
        }
    }

    public static byte[] aesEncryptCBCBytes(String source)  {
        try {
            String aesEncryptCBC = aesEncryptCBC(source);

            return aesEncryptCBC.getBytes(StandardCharsets.UTF_8);
        } catch (Exception e) {
            logger.error("[aesEncryptCBCBytes exception]: {}", E2s.exception2String(e));
            throw new BizException(MessageEnum.EXCEPTION.getMessage());
        }
    }

    /**
     * AES CBC 解码
     * @param aesStr 待解码的字符
     * @return 源字符
     */
    public static String aesDecryptCBC(String aesStr) {
        try {
            Cipher cipher = Cipher.getInstance(CBC_ALGORITHMSTR);
            IvParameterSpec iv = new IvParameterSpec(IV);
            byte[] keyBytes = ENCRYPT_KEY.getBytes(StandardCharsets.UTF_8);

            SecretKeySpec skySpec = new SecretKeySpec(keyBytes, "AES");
            cipher.init(Cipher.DECRYPT_MODE, skySpec, iv);
            byte[] original  = cipher.doFinal(Base64.decodeBase64(aesStr));

            return new String(original, StandardCharsets.UTF_8);
        } catch (Exception e) {
            if (e instanceof IllegalBlockSizeException) {
                throw new BizException(MessageEnum.USER_ACCOUNT_PASSWORD_INVALID.getMessage());
            }

            logger.error("[aesDecryptCBC exception]: {}", E2s.exception2String(e));
            throw new BizException(MessageEnum.EXCEPTION.getMessage());
        }
    }

    // 解析逗号分隔的id集合
    public static List<Long> parseIdListStr(String ids) {
        String[] split = ids.split(",");
        Assert.isTrue(split.length < 1, MessageEnum.ID_EMPTY.getMessage());

        List<Long> idList = new ArrayList<>();
        for (String s : split) {
            if (Assert.notEmpty(s)) {
                idList.add(Long.parseLong(s.trim()));
            }
        }

        return idList;
    }

    public static String getListStr(List<?> list) {
        return getListStrByTag(list, ",");
    }
    public static String getListStrByTag(List<?> list, String tag) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < list.size(); i++) {
            Object item = list.get(i);
            if (i > list.size() - 2) {
                sb.append(item);
                break;
            }
            sb.append(item).append(tag);
        }
        return sb.toString();
    }

    public static String createSecurityCode(Map<String, Object> argMap) {
        StringBuffer sb = new StringBuffer();
        argMap.forEach((k,v) -> sb.append(k).append("=").append(v).append("&"));
        sb.append("encryptKey=").append(ENCRYPT_KEY);
        return MD5(sb.toString());
    }

    public static List<String> parseStrListStr(String ids) {
        String[] split = ids.split(",");
        Assert.isTrue(split.length < 1, MessageEnum.ID_EMPTY.getMessage());

        List<String> strList = new ArrayList<>();
        for (String s : split) {
            if (Assert.notEmpty(s)) {
                strList.add(s.trim());
            }
        }

        return strList;
    }
}
